#pragma once

#include "Common.hpp"
#include "PageCache.hpp"

class CentralCache :public PtrHelper
{
	CentralCache() {}
	CentralCache(const CentralCache&) = delete;
	CentralCache& operator= (const CentralCache&) = delete;

private:
	static CentralCache _centralcache;
	SpanList _spanList[NFREELIST];
public:
	static CentralCache& GetInstanse()
	{
		return _centralcache;
	}

	// 从大小为 byte_size 的链表中，获取一个 Span
	Span* GetOneSpan(SpanList& list, size_t byte_size, std::unique_lock<std::mutex>& list_lock)
	{
		Span* it = list.Begin();
		
		// 遍历当前 大小为 byte_size 的桶，寻找 _freeList 不为空的内存节点
		while (it != list.End())
		{
			if (it->_freeList != nullptr)
			{
				return it;
			}
			else
			{
				it = it->_next;
			}
		}

		// 这里可以将 CentralCache 中的桶解锁，解锁后可以让其他线程归还属于这个桶的内存，否则归还内存时也会阻塞
		list_lock.unlock();


		// 走到这里说明中心内存没有空闲区间，那么就去 页内存(PageCache) 上申请
															// 计算一个合适的大小去申请
		Span* span;
		{
			// 对获取 PageCache 加锁
			std::unique_lock<std::mutex> lock(PageCache::GetInstanse()._mutex);
			span = PageCache::GetInstanse().newSpan(SizeClass::NumMovePage(byte_size));

			// 获取到之后，将 span 标记修改为已经被使用
			span->_isUsed = true;
			span->_objSize = byte_size;  // 记录当前 Span 切分的大小
		}

		//!!!  对 Span 进行切分，不需要上锁，这里其他线程不会访问到，因为还没有挂接到 _spanList 上面

		// 计算从 PageCache 中获得的内存的起始地址 start ; start = ID * 8k (PAGE_SHIFT)
		char* start = (char*)(span->_pageId << PAGE_SHIFT);
		// 获取的内存大小为 Span->_n 的数量 * 每页的大小 = get_bytes
		size_t get_bytes = span->_n << PAGE_SHIFT;
		// end 是每个 span 内的内存的结尾的后一个地址,end 前一个地址是 链表最后一个节点
		char* end = start + get_bytes;


		// 把大块内存切分
		span->_freeList = start;
		start += byte_size;
		void* tail = span->_freeList;  // tail 是头部指针

		// 将内存一块一块分割连接
		while (start < end)
		{
			// 前一块指针连接后一块内存的头部
			tail = NextObj(tail) = start;
			//tail = start;
			start += byte_size;
		}

		// !!!!!!  对内存的链表置空
		NextObj(tail) = nullptr;

		// 在对桶上锁
		list_lock.lock();
		list.PushFront(span);

		return span;
	}

	// 从中心内存获取 batchNum 个大小为 byte_size 的内存块
	size_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t byte_size)
	{
		size_t index = SizeClass::Index(byte_size);

		{
			// 上锁
			std::unique_lock<std::mutex> list_lock(_spanList[index]._mutex);

			// 从大小为 byte_size 的链表中，获取一个 Span
			Span* span = GetOneSpan(_spanList[index], byte_size, list_lock);

			// GetOneSpan 中，已经把这个 span 放入_spanList 中了，这里必须加锁，否则可能被其他线程访问到
			assert(span);
			assert(span->_freeList);

			// 获取的内存块的首位 通过引用参数传递给外部函数
			end = start = span->_freeList;

			size_t i = 1;
			while (i < batchNum && NextObj(end) != nullptr)  // 需要判断后面是否还有内存块
			{
				end = NextObj(end);
				i++;
			}

			// 到这里已经确定了内存区间的 尾部

			span->_freeList = NextObj(end);
			NextObj(end) = nullptr;

			span->_useCount += i;

			return i;  // 返回实际获取的内存数
		}

	}

	// 将 ThreadCache 中的内存 还给 CentralCache 的 Span
	void ReleaseListToSpans(void* start, size_t byte_size)
	{
		size_t index = SizeClass::Index(byte_size);

		std::unique_lock<std::mutex> list_lock(_spanList[index]._mutex);

		while (start)
		{	// 依次将 freelist 中的空闲块，还给 span
			void* next = NextObj(start);
			// 这里堆桶进行操作，加桶锁
			{
				// 给出地址，根据 PageChache 的Hash,将对应span的地址取出来进行归还
				Span* span = PageCache::GetInstanse().MapObjectToSpan(start);
				NextObj(start) = span->_freeList;
				span->_freeList = start;

				if (--span->_useCount == 0)
				{
					// 如果 _usecount == 0 , 说明span 中分配出去的所有内存块全都归还了，可以进行下一步的释放

					// 1. 先从当前 _spanList 中删除
					_spanList[index].Erase(span);

					// 这里可以解除 桶锁，让其他线程可以访问桶
					list_lock.unlock();

					// 2. 对span 内的内存进行置空
					span->_freeList = nullptr;
					span->_next = nullptr;
					span->_prev = nullptr;

					// 3. 还给下一层
					{
						// PageCahce 加锁
						std::unique_lock<std::mutex> lock(PageCache::GetInstanse()._mutex);
						PageCache::GetInstanse().ReleaseSpanToPageCache(span);
					}
					list_lock.lock();
				}
			}
			start = next;

		}
	}



};

// 静态变量需要先声明
CentralCache CentralCache::_centralcache;